# coding: utf-8
import importlib
import os
import random

import jwt
from flask import request, current_app
from jinja2 import Environment
from sqlalchemy import and_, or_, text

import extentions.db
from bluesky.dto.exception import BizException
from bluesky.util import constant

table_rs_cls_map = {}
rtype_table_map = {}
resource_type_map = {}

op_map = {
    'eq': '=',
    'like': 'like',
    'like_left': 'like',
    'like_right': 'like',
    'not_like': 'not like',
    'not_like_left': 'not like',
    'not_like_right': 'not like',
    'in': 'in',
    'not_in': 'not in',
    'ne': '!=',
    'ge': '>=',
    'gt': '>',
    'le': '<=',
    'lt': '<',
    'is_null': 'is null',
    'not_null': 'is not null',
    'json_contains': 'json_contains',
}

relate_type_map = {
    'and': and_,
    'or': or_
}


def error_handler(e):
    if isinstance(e, BizException):
        return {'code': e.code, 'msg': e.msg, 'error': e.error}
    return {'code': 500, 'msg': e.args[0]}


def judge_clz(rtype):
    table = rtype_table_map.get(rtype)
    cls = table_rs_cls_map.get(table)
    return cls


def empty(s):
    return s == '' or s is None


def random_id():
    return random_digit_letter(20)


def random_digit_letter(length):
    choose = constant.DIGIT + constant.LOWER + constant.UPPER
    ret = ''
    for i in range(length):
        ret = ret + choose[random.randint(0, len(choose) - 1)]
    return ret


def set_rs_value(rs, field: str, value):
    if field.startswith('ext.'):
        ext_key = field[4:]
        set_value(rs.ext, ext_key, value)
    else:
        setattr(rs, field, value)


def set_value(d: dict, field: str, value):
    fields = field.split('.')
    set_value_from_arr_key(d, fields, value)


def set_value_from_arr_key(d: dict, fields: [str], value):
    if not fields or len(fields) == 0 or not d:
        return None
    if len(fields) == 1:
        d[fields[0]] = value
    field = fields.pop(0)
    v = d.get(field)
    set_value_from_arr_key(v, field, value)


def get_rs_value(rs, field: str):
    if field.startswith('ext.'):
        ext_key = field[4:]
        return get_value(rs.ext, ext_key)
    else:
        return getattr(rs, field)


def get_value(d: dict, field: str):
    fields = field.split('.')
    return get_value_from_arr_key(d, fields)


def get_value_from_arr_key(d: dict, fields: [str]):
    if not fields or len(fields) == 0 or not d:
        return None
    if len(fields) == 1:
        return d.get(fields[0])
    field = fields.pop(0)
    v = d.get(field)
    return get_value_from_arr_key(v, field)


def get_resource_type_config(rtype: str, action: str):
    rtype_map = resource_type_map
    rt = rtype_map.get(rtype)
    rt_ext = rt.ext

    config = None
    configs = rt_ext.get('config')
    if configs:
        match_configs = list(filter(lambda t: t.get('action') == action, configs))
        if match_configs and len(match_configs) > 0:
            config = match_configs[0]
    return config


def remove_fields(result: dict, action: str):
    if not result or not action:
        return
    rtype = result['type']
    config = get_resource_type_config(rtype, action)
    if not config:
        return
    flat_ext_outer = config.get('flat_ext_outer') or True
    return_fields = config.get('return_fields')
    if not result or not return_fields:
        if flat_ext_outer and result.get('ext'):
            result.update(result.get('ext'))
            del result['ext']
        return

    outer_return_fields = set(filter(lambda x: not x.startswith('ext.'), return_fields))
    outer_result_keys = result.keys() - 'ext'
    outer_delete_keys = outer_result_keys - outer_return_fields
    for key in outer_delete_keys:
        del result[key]

    ext_return_keys = list(filter(lambda x: x.startswith('ext.'), return_fields))
    ext_substr_return_keys = set(map(lambda x: x[4:], ext_return_keys))
    ext = result.get('ext')
    if ext and ext_substr_return_keys:
        ext_result_keys = ext.keys()
        ext_delete_keys = ext_result_keys - ext_substr_return_keys
        for key in ext_delete_keys:
            del ext[key]
    if flat_ext_outer and ext:
        result.update(ext)
        del result['ext']


def import_all_files(module_name):
    module = importlib.import_module(module_name)
    module_path = os.path.dirname(module.__file__)
    for file_name in os.listdir(module_path):
        if file_name.endswith('.py') and not file_name.startswith('__'):
            importlib.import_module(f"{module_name}.{file_name[:-3]}")


def render(tpl: str, param: dict):
    if '{{' in tpl and '}}' in tpl:
        env = Environment()
        for k in param:
            env.globals[k] = param[k]
        template = env.from_string(tpl)
        result = template.render()
        return result
    return tpl


def request_context(action):
    return {
        'body': request.get_json(),
        'query': request.args,
        'header': request.headers,
        'action': action,
        'session': extentions.db.new_session(),
    }


def get_page_num(data):
    if not data:
        return 1
    return data.get('page_num') or 1


def get_page_size(data):
    if not data:
        return 10
    return data.get('page_size') or 10


def get_limit(data):
    page_size = get_page_size(data)
    return page_size


def get_offset(data):
    page_num = get_page_num(data)
    page_size = get_page_size(data)
    return (page_num - 1) * page_size


def process_count_query(query, param):
    param_filter = process_item(param)
    if param_filter is not None:
        query = query.filter(param_filter)
    return query


def process_query(query, param):
    param_filter = process_item(param)
    if param_filter is not None:
        query = query.filter(param_filter)

    order_by = param.get('order_by')
    page_num = param.get('page_num')
    page_size = param.get('page_size')
    # 排序
    if order_by:
        query = query.order_by(text(order_by))

    # 分页
    if page_num is not None and page_size is not None:
        query = query.limit(int(page_size)).offset((int(page_num) - 1) * int(page_size))
    return query


def process_item(param):
    items = param.get('items')
    filters = []

    for key in param:
        if key in ['items', 'item_relate_type', 'relate_type', 'page_num', 'page_size', 'order_by']:
            continue

        filter_key = get_filter_key(key)
        val = param[key]
        val_key = f'val_{key.replace(".", "_")}_{random_digit_letter(4)}'
        if isinstance(val, dict):
            if condition_skip(val, param):
                continue
            op = val.get('op') or 'eq'
            sql_op = op_map.get(op)
            f_val = val.get('value')

            if op in ['is_null', 'not_null']:
                filters.append(text(f'{filter_key} {sql_op}'))
            elif op in ['like', 'not_like']:
                filters.append(text(f"{filter_key} {sql_op} '%' :{val_key} '%'").params({f'{val_key}': f_val}))
            elif op in ['like_left', 'not_like_left']:
                filters.append(text(f"{filter_key} {sql_op} :{val_key} '%'").params({f'{val_key}': f_val}))
            elif op in ['like_right', 'not_like_right']:
                filters.append(text(f"{filter_key} {sql_op} '%' :{val_key}").params({f'{val_key}': f_val}))
            elif op in ['json_contains']:
                filter_value = f'"{f_val}"' if isinstance(f_val, str) else str(f_val)
                filter_param = {f'{val_key}': filter_value}
                filters.append(text(f'json_contains({filter_key}, :{val_key})').params(filter_param))
            else:
                filters.append(text(f'{filter_key} {sql_op} :{val_key}').params({f'{val_key}': f_val}))
        else:
            filters.append(text(f'{filter_key} = :{val_key}').params({f'{val_key}': val}))

    if items:
        item_filters = []
        for itm in items:
            item_filters.append(process_item(itm))
        sql_item_relate_type = relate_type_map[param.get('item_relate_type') or 'and']
        filters.append(sql_item_relate_type(*item_filters))

    sql_relate_type = relate_type_map[param.get('relate_type') or 'and']
    return sql_relate_type(*filters)


def get_filter_key(key):
    filter_key = key
    if '.' in key:
        ext = key[:key.index('.')]
        inner_key = key[key.index('.') + 1:]
        filter_key = f"{ext} ->> '$.{inner_key}'"
    return filter_key


def condition_skip(val, param):
    condition = val.get('condition')
    if condition:
        render_val = render(condition, param)
        if 'True' != render_val:
            return True
    return False


def generate_token(user):
    payload = {
        'id': user.id,
    }
    app = current_app
    token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256')
    return token


def decode_token(token):
    app = current_app
    result = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
    return result

